#!/usr/bin/env python2

"""
    xcvrd
    Transceiver information update daemon for SONiC
"""

try:
    import os
    import sys
    import time
    import signal
    import threading
    import json
    import string
    import ast
    import multiprocessing
    from swsscommon import swsscommon
    from sonic_daemon_base import daemon_base
    from sonic_daemon_base.daemon_base import Logger
    from sonic_daemon_base.daemon_base import DaemonBase
    from enum import Enum
except ImportError, e:
    raise ImportError (str(e) + " - required module not found")

#
# Constants ====================================================================
#

SYSLOG_IDENTIFIER = "xcvrd"

PLATFORM_SPECIFIC_MODULE_NAME = "sfputil"
PLATFORM_SPECIFIC_CLASS_NAME = "SfpUtil"

TRANSCEIVER_INFO_TABLE = 'TRANSCEIVER_INFO'
TRANSCEIVER_DOM_SENSOR_TABLE = 'TRANSCEIVER_DOM_SENSOR'
TRANSCEIVER_STATUS_TABLE = 'TRANSCEIVER_STATUS'

SELECT_TIMEOUT_MSECS = 1000

DOM_INFO_UPDATE_PERIOD_SECS = 60
TIME_FOR_SFP_READY_SECS = 1
XCVRD_MAIN_THREAD_SLEEP_SECS = 60

# SFP status definition, shall be aligned with the definition in get_change_event() of ChassisBase
SFP_STATUS_REMOVED = '0'
SFP_STATUS_INSERTED = '1'

# SFP error code enum, new elements can be added to the enum if new errors need to be supported.
SFP_STATUS_ERR_ENUM = Enum('SFP_STATUS_ERR_ENUM', ['SFP_STATUS_ERR_I2C_STUCK', 'SFP_STATUS_ERR_BAD_EEPROM', 
                                                  'SFP_STATUS_ERR_UNSUPPORTED_CABLE', 'SFP_STATUS_ERR_HIGH_TEMP',
                                                  'SFP_STATUS_ERR_BAD_CABLE'], start=2)

# Convert the error code to string and store them in a set for convenience
errors_block_eeprom_reading = set(str(error_code.value) for error_code in SFP_STATUS_ERR_ENUM)

EVENT_ON_ALL_SFP = '-1'
# events definition
SYSTEM_NOT_READY = 'system_not_ready'
SYSTEM_BECOME_READY = 'system_become_ready'
SYSTEM_FAIL = 'system_fail'
NORMAL_EVENT = 'normal'
# states definition
STATE_INIT = 0
STATE_NORMAL = 1
STATE_EXIT = 2

PHYSICAL_PORT_NOT_EXIST = -1
SFP_EEPROM_NOT_READY = -2

SFPUTIL_LOAD_ERROR = 1
PORT_CONFIG_LOAD_ERROR = 2
NOT_IMPLEMENTED_ERROR = 3

RETRY_TIMES_FOR_SYSTEM_READY = 24
RETRY_PERIOD_FOR_SYSTEM_READY_MSECS = 5000

RETRY_TIMES_FOR_SYSTEM_FAIL = 24
RETRY_PERIOD_FOR_SYSTEM_FAIL_MSECS = 5000

TEMP_UNIT = 'C'
VOLT_UNIT = 'Volts'
POWER_UNIT = 'dBm'
BIAS_UNIT = 'mA'

media_settings = ''
g_dict = {}
# Global platform specific sfputil class instance
platform_sfputil = None
# Global chassis object based on new platform api
platform_chassis = None

# Global logger class instance
logger = Logger(SYSLOG_IDENTIFIER)

#
# Helper functions =============================================================
#

# Find out the underneath physical port list by logical name
def logical_port_name_to_physical_port_list(port_name):
    if port_name.startswith("Ethernet"):
        if platform_sfputil.is_logical_port(port_name):
            return platform_sfputil.get_logical_to_physical(port_name)
        else:
            logger.log_error("Invalid port '%s'" % port_name)
            return None
    else:
        return [int(port_name)]

# Get physical port name
def get_physical_port_name(logical_port, physical_port, ganged):
    if logical_port == physical_port:
        return logical_port
    elif ganged:
        return logical_port + ":%d (ganged)" % physical_port
    else:
        return logical_port

# Strip units and beautify
def strip_unit_and_beautify(value, unit):
    # Strip unit from raw data
    if type(value) is str:
        width = len(unit)
        if value[-width:] == unit:
            value = value[:-width]
        return value
    else:
        return str(value)

def _wrapper_get_presence(physical_port):
    if platform_chassis is not None:
        try:
            return platform_chassis.get_sfp(physical_port).get_presence()
        except NotImplementedError:
            pass
    return platform_sfputil.get_presence(physical_port)

def _wrapper_get_transceiver_info(physical_port):
    if platform_chassis is not None:
        try:
            return platform_chassis.get_sfp(physical_port).get_transceiver_info()
        except NotImplementedError:
            pass
    return platform_sfputil.get_transceiver_info_dict(physical_port)

def _wrapper_get_transceiver_dom_info(physical_port):
    if platform_chassis is not None:
        try:
            return platform_chassis.get_sfp(physical_port).get_transceiver_bulk_status()
        except NotImplementedError:
            pass
    return platform_sfputil.get_transceiver_dom_info_dict(physical_port)

def _wrapper_get_transceiver_dom_threshold_info(physical_port):
    if platform_chassis is not None:
        try:
            return platform_chassis.get_sfp(physical_port).get_transceiver_threshold_info()
        except NotImplementedError:
            pass
    return platform_sfputil.get_transceiver_dom_threshold_info_dict(physical_port)

def _wrapper_get_transceiver_change_event(timeout):
    if platform_chassis is not None:
        try:
            status, events =  platform_chassis.get_change_event(timeout)
            sfp_events = events['sfp']
            return status, sfp_events
        except NotImplementedError:
            pass
    return platform_sfputil.get_transceiver_change_event(timeout)

# Remove unnecessary unit from the raw data
def beautify_dom_info_dict(dom_info_dict):
    dom_info_dict['temperature'] = strip_unit_and_beautify(dom_info_dict['temperature'], TEMP_UNIT)
    dom_info_dict['voltage'] = strip_unit_and_beautify(dom_info_dict['voltage'], VOLT_UNIT)
    dom_info_dict['rx1power'] = strip_unit_and_beautify(dom_info_dict['rx1power'], POWER_UNIT)
    dom_info_dict['rx2power'] = strip_unit_and_beautify(dom_info_dict['rx2power'], POWER_UNIT)
    dom_info_dict['rx3power'] = strip_unit_and_beautify(dom_info_dict['rx3power'], POWER_UNIT)
    dom_info_dict['rx4power'] = strip_unit_and_beautify(dom_info_dict['rx4power'], POWER_UNIT)
    dom_info_dict['tx1bias'] = strip_unit_and_beautify(dom_info_dict['tx1bias'], BIAS_UNIT)
    dom_info_dict['tx2bias'] = strip_unit_and_beautify(dom_info_dict['tx2bias'], BIAS_UNIT)
    dom_info_dict['tx3bias'] = strip_unit_and_beautify(dom_info_dict['tx3bias'], BIAS_UNIT)
    dom_info_dict['tx4bias'] = strip_unit_and_beautify(dom_info_dict['tx4bias'], BIAS_UNIT)
    dom_info_dict['tx1power'] = strip_unit_and_beautify(dom_info_dict['tx1power'], POWER_UNIT)
    dom_info_dict['tx2power'] = strip_unit_and_beautify(dom_info_dict['tx2power'], POWER_UNIT)
    dom_info_dict['tx3power'] = strip_unit_and_beautify(dom_info_dict['tx3power'], POWER_UNIT)
    dom_info_dict['tx4power'] = strip_unit_and_beautify(dom_info_dict['tx4power'], POWER_UNIT)

def beautify_dom_threshold_info_dict(dom_info_dict):
    dom_info_dict['temphighalarm'] = strip_unit_and_beautify(dom_info_dict['temphighalarm'], TEMP_UNIT)
    dom_info_dict['temphighwarning'] = strip_unit_and_beautify(dom_info_dict['temphighwarning'], TEMP_UNIT)
    dom_info_dict['templowalarm'] = strip_unit_and_beautify(dom_info_dict['templowalarm'], TEMP_UNIT)
    dom_info_dict['templowwarning'] = strip_unit_and_beautify(dom_info_dict['templowwarning'], TEMP_UNIT)

    dom_info_dict['vcchighalarm'] = strip_unit_and_beautify(dom_info_dict['vcchighalarm'], VOLT_UNIT)
    dom_info_dict['vcchighwarning'] = strip_unit_and_beautify(dom_info_dict['vcchighwarning'], VOLT_UNIT)
    dom_info_dict['vcclowalarm'] = strip_unit_and_beautify(dom_info_dict['vcclowalarm'], VOLT_UNIT)
    dom_info_dict['vcclowwarning'] = strip_unit_and_beautify(dom_info_dict['vcclowwarning'], VOLT_UNIT)

    dom_info_dict['txpowerhighalarm'] = strip_unit_and_beautify(dom_info_dict['txpowerhighalarm'], POWER_UNIT)
    dom_info_dict['txpowerlowalarm'] = strip_unit_and_beautify(dom_info_dict['txpowerlowalarm'], POWER_UNIT)
    dom_info_dict['txpowerhighwarning'] = strip_unit_and_beautify(dom_info_dict['txpowerhighwarning'], POWER_UNIT)
    dom_info_dict['txpowerlowwarning'] = strip_unit_and_beautify(dom_info_dict['txpowerlowwarning'], POWER_UNIT)

    dom_info_dict['rxpowerhighalarm'] = strip_unit_and_beautify(dom_info_dict['rxpowerhighalarm'], POWER_UNIT)
    dom_info_dict['rxpowerlowalarm'] = strip_unit_and_beautify(dom_info_dict['rxpowerlowalarm'], POWER_UNIT)
    dom_info_dict['rxpowerhighwarning'] = strip_unit_and_beautify(dom_info_dict['rxpowerhighwarning'], POWER_UNIT)
    dom_info_dict['rxpowerlowwarning'] = strip_unit_and_beautify(dom_info_dict['rxpowerlowwarning'], POWER_UNIT)

    dom_info_dict['txbiashighalarm'] = strip_unit_and_beautify(dom_info_dict['txbiashighalarm'], BIAS_UNIT)
    dom_info_dict['txbiaslowalarm'] = strip_unit_and_beautify(dom_info_dict['txbiaslowalarm'], BIAS_UNIT)
    dom_info_dict['txbiashighwarning'] = strip_unit_and_beautify(dom_info_dict['txbiashighwarning'], BIAS_UNIT)
    dom_info_dict['txbiaslowwarning'] = strip_unit_and_beautify(dom_info_dict['txbiaslowwarning'], BIAS_UNIT)

# Update port sfp info in db
def post_port_sfp_info_to_db(logical_port_name, table, transceiver_dict, 
                             stop_event=threading.Event()):
    ganged_port = False
    ganged_member_num = 1

    physical_port_list = logical_port_name_to_physical_port_list(logical_port_name)
    if physical_port_list is None:
        logger.log_error("No physical ports found for logical port '%s'" % logical_port_name)
        return PHYSICAL_PORT_NOT_EXIST

    if len(physical_port_list) > 1:
        ganged_port = True

    for physical_port in physical_port_list:
        if stop_event.is_set():
            break

        if not _wrapper_get_presence(physical_port):
            continue

        port_name = get_physical_port_name(logical_port_name, ganged_member_num, ganged_port)
        ganged_member_num += 1

        try:
            port_info_dict = _wrapper_get_transceiver_info(physical_port)
            if port_info_dict is not None:
                transceiver_dict[physical_port]=port_info_dict
                fvs = swsscommon.FieldValuePairs([('type', port_info_dict['type']),
                                                  ('hardware_rev', port_info_dict['hardware_rev']),
                                                  ('serial', port_info_dict['serial']),
                                                  ('manufacturer', port_info_dict['manufacturer']),
                                                  ('model', port_info_dict['model']),
                                                  ('vendor_oui',port_info_dict['vendor_oui']),
                                                  ('vendor_date',port_info_dict['vendor_date']),
                                                  ('connector',port_info_dict['connector']),
                                                  ('encoding',port_info_dict['encoding']),
                                                  ('ext_identifier',port_info_dict['ext_identifier']),
                                                  ('ext_rateselect_compliance',port_info_dict['ext_rateselect_compliance']),
                                                  ('cable_type',port_info_dict['cable_type']),
                                                  ('cable_length',port_info_dict['cable_length']),
                                                  ('specification_compliance',port_info_dict['specification_compliance']),
                                                  ('nominal_bit_rate',port_info_dict['nominal_bit_rate']),
                                                  ('application_advertisement',port_info_dict['application_advertisement'])])
                table.set(port_name, fvs)
            else:
                return SFP_EEPROM_NOT_READY

        except NotImplementedError:
            logger.log_error("This functionality is currently not implemented for this platform")
            sys.exit(NOT_IMPLEMENTED_ERROR)

# Update port dom threshold info in db
def post_port_dom_threshold_info_to_db(logical_port_name, table,
                                       stop=threading.Event()):
    ganged_port = False
    ganged_member_num = 1

    physical_port_list = logical_port_name_to_physical_port_list(logical_port_name)
    if physical_port_list is None:
        logger.log_error("No physical ports found for logical port '%s'"
                                                           % logical_port_name)
        return PHYSICAL_PORT_NOT_EXIST

    if len(physical_port_list) > 1:
        ganged_port = True

    for physical_port in physical_port_list:
        if stop.is_set():
            break

        if not _wrapper_get_presence(physical_port):
            continue

        port_name = get_physical_port_name(logical_port_name,
                                           ganged_member_num, ganged_port)
        ganged_member_num += 1

        try:
            dom_info_dict = _wrapper_get_transceiver_dom_threshold_info(physical_port)
            if dom_info_dict is not None:
                beautify_dom_threshold_info_dict(dom_info_dict)
                fvs = swsscommon.FieldValuePairs(
                        [('temphighalarm', dom_info_dict['temphighalarm']),
                         ('temphighwarning', dom_info_dict['temphighwarning']),
                         ('templowalarm', dom_info_dict['templowalarm']),
                         ('templowwarning', dom_info_dict['templowwarning']),
                         ('vcchighalarm', dom_info_dict['vcchighalarm']),
                         ('vcchighwarning', dom_info_dict['vcchighwarning']),
                         ('vcclowalarm', dom_info_dict['vcclowalarm']),
                         ('vcclowwarning', dom_info_dict['vcclowwarning']),
                         ('txpowerhighalarm', dom_info_dict['txpowerhighalarm']),
                         ('txpowerlowalarm', dom_info_dict['txpowerlowalarm']),
                         ('txpowerhighwarning', dom_info_dict['txpowerhighwarning']),
                         ('txpowerlowwarning', dom_info_dict['txpowerlowwarning']),
                         ('rxpowerhighalarm', dom_info_dict['rxpowerhighalarm']),
                         ('rxpowerlowalarm', dom_info_dict['rxpowerlowalarm']),
                         ('rxpowerhighwarning', dom_info_dict['rxpowerhighwarning']),
                         ('rxpowerlowwarning', dom_info_dict['rxpowerlowwarning']),
                         ('txbiashighalarm', dom_info_dict['txbiashighalarm']),
                         ('txbiaslowalarm', dom_info_dict['txbiaslowalarm']),
                         ('txbiashighwarning', dom_info_dict['txbiashighwarning']),
                         ('txbiaslowwarning', dom_info_dict['txbiaslowwarning'])
                        ])
                table.set(port_name, fvs)
            else:
                return SFP_EEPROM_NOT_READY

        except NotImplementedError:
            logger.log_error("This functionality is currently not implemented for this platform")
            sys.exit(NOT_IMPLEMENTED_ERROR)

# Update port dom sensor info in db
def post_port_dom_info_to_db(logical_port_name, table, stop_event=threading.Event()):
    ganged_port = False
    ganged_member_num = 1

    physical_port_list = logical_port_name_to_physical_port_list(logical_port_name)
    if physical_port_list is None:
        logger.log_error("No physical ports found for logical port '%s'" % logical_port_name)
        return PHYSICAL_PORT_NOT_EXIST

    if len(physical_port_list) > 1:
        ganged_port = True

    for physical_port in physical_port_list:
        if stop_event.is_set():
            break

        if not _wrapper_get_presence(physical_port):
            continue

        port_name = get_physical_port_name(logical_port_name, ganged_member_num, ganged_port)
        ganged_member_num += 1

        try:
            dom_info_dict = _wrapper_get_transceiver_dom_info(physical_port)
            if dom_info_dict is not None:
                beautify_dom_info_dict(dom_info_dict)
                if platform_chassis.get_sfp(physical_port).sfp_type == 'QSFP_DD':
                    fvs = swsscommon.FieldValuePairs(
                        [('temperature', dom_info_dict['temperature']),
                         ('voltage', dom_info_dict['voltage']),
                         ('rx1power', dom_info_dict['rx1power']),
                         ('rx2power', dom_info_dict['rx2power']),
                         ('rx3power', dom_info_dict['rx3power']),
                         ('rx4power', dom_info_dict['rx4power']),
                         ('rx5power', dom_info_dict['rx5power']),
                         ('rx6power', dom_info_dict['rx6power']),
                         ('rx7power', dom_info_dict['rx7power']),
                         ('rx8power', dom_info_dict['rx8power']),
                         ('tx1bias', dom_info_dict['tx1bias']),
                         ('tx2bias', dom_info_dict['tx2bias']),
                         ('tx3bias', dom_info_dict['tx3bias']),
                         ('tx4bias', dom_info_dict['tx4bias']),
                         ('tx5bias', dom_info_dict['tx5bias']),
                         ('tx6bias', dom_info_dict['tx6bias']),
                         ('tx7bias', dom_info_dict['tx7bias']),
                         ('tx8bias', dom_info_dict['tx8bias']),
                         ('tx1power', dom_info_dict['tx1power']),
                         ('tx2power', dom_info_dict['tx2power']),
                         ('tx3power', dom_info_dict['tx3power']),
                         ('tx4power', dom_info_dict['tx4power']),
                         ('tx5power', dom_info_dict['tx5power']),
                         ('tx6power', dom_info_dict['tx6power']),
                         ('tx7power', dom_info_dict['tx7power']),
                         ('tx8power', dom_info_dict['tx8power'])
                        ])
                else:
                    fvs = swsscommon.FieldValuePairs(
                            [('temperature', dom_info_dict['temperature']),
                            ('voltage', dom_info_dict['voltage']),
                            ('rx1power', dom_info_dict['rx1power']),
                            ('rx2power', dom_info_dict['rx2power']),
                            ('rx3power', dom_info_dict['rx3power']),
                            ('rx4power', dom_info_dict['rx4power']),
                            ('tx1bias', dom_info_dict['tx1bias']),
                            ('tx2bias', dom_info_dict['tx2bias']),
                            ('tx3bias', dom_info_dict['tx3bias']),
                            ('tx4bias', dom_info_dict['tx4bias']),
                            ('tx1power', dom_info_dict['tx1power']),
                            ('tx2power', dom_info_dict['tx2power']),
                            ('tx3power', dom_info_dict['tx3power']),
                            ('tx4power', dom_info_dict['tx4power'])
                            ])

                table.set(port_name, fvs)

            else:
                return SFP_EEPROM_NOT_READY

        except NotImplementedError:
            logger.log_error("This functionality is currently not implemented for this platform")
            sys.exit(NOT_IMPLEMENTED_ERROR)

# Update port dom/sfp info in db
def post_port_sfp_dom_info_to_db(is_warm_start, stop_event=threading.Event()):
    # Connect to STATE_DB and create transceiver dom/sfp info tables
    transceiver_dict = {}
    state_db = daemon_base.db_connect("STATE_DB")
    int_tbl = swsscommon.Table(state_db, TRANSCEIVER_INFO_TABLE)
    dom_tbl = swsscommon.Table(state_db, TRANSCEIVER_DOM_SENSOR_TABLE)

    appl_db = daemon_base.db_connect("APPL_DB")
    app_port_tbl = swsscommon.ProducerStateTable(appl_db,
                                                 swsscommon.APP_PORT_TABLE_NAME)

    # Post all the current interface dom/sfp info to STATE_DB
    logical_port_list = platform_sfputil.logical
    for logical_port_name in logical_port_list:
        if stop_event.is_set():
            break

        post_port_sfp_info_to_db(logical_port_name, int_tbl, transceiver_dict, stop_event)
        post_port_dom_info_to_db(logical_port_name, dom_tbl, stop_event)
        post_port_dom_threshold_info_to_db(logical_port_name, dom_tbl, stop_event)

        ## Do not notify media settings during warm reboot to avoid dataplane traffic impact
        if is_warm_start == False:
            notify_media_setting(logical_port_name, transceiver_dict, app_port_tbl)
            transceiver_dict.clear()

# Delete port dom/sfp info from db
def del_port_sfp_dom_info_from_db(logical_port_name, int_tbl, dom_tbl):
    ganged_port = False
    ganged_member_num = 1

    physical_port_list = logical_port_name_to_physical_port_list(logical_port_name)
    if physical_port_list is None:
        logger.log_error("No physical ports found for logical port '%s'" % logical_port_name)
        return PHYSICAL_PORT_NOT_EXIST

    if len(physical_port_list) > 1:
        ganged_port = True

    for physical_port in physical_port_list:
        port_name = get_physical_port_name(logical_port_name, ganged_member_num, ganged_port)
        ganged_member_num += 1

        try:
            if int_tbl != None:
                int_tbl._del(port_name)
            if dom_tbl != None:
                dom_tbl._del(port_name)

        except NotImplementedError:
            logger.log_error("This functionality is currently not implemented for this platform")
            sys.exit(NOT_IMPLEMENTED_ERROR)

# recover missing sfp table entries if any
def recover_missing_sfp_table_entries(sfp_util, int_tbl, status_tbl, stop_event):
    transceiver_dict = {}
    
    keys = int_tbl.getKeys()
    logical_port_list = sfp_util.logical
    for logical_port_name in logical_port_list:
        if stop_event.is_set():
            break
        if logical_port_name not in keys and not detect_port_in_error_status(logical_port_name, status_tbl):
            post_port_sfp_info_to_db(logical_port_name, int_tbl, transceiver_dict, stop_event)
    

def check_port_in_range(range_str, physical_port):
    range_separator = '-'
    range_list = range_str.split(range_separator)
    start_num = int(range_list[0].strip())
    end_num = int(range_list[1].strip())
    if start_num <= physical_port <= end_num:
        return True
    return False


def get_media_settings_value(physical_port, key):
    range_separator = '-'
    comma_separator = ','
    media_dict = {}
    default_dict = {}

    # Keys under global media settings can be a list or range or list of ranges
    # of physical port numbers. Below are some examples
    # 1-32
    # 1,2,3,4,5
    # 1-4,9-12

    if "GLOBAL_MEDIA_SETTINGS" in g_dict:
        for keys in g_dict["GLOBAL_MEDIA_SETTINGS"]:
            if comma_separator in keys:
                port_list = keys.split(comma_separator)
                for port in port_list:
                    if range_separator in port:
                        if check_port_in_range(port, physical_port):
                            media_dict = g_dict["GLOBAL_MEDIA_SETTINGS"][keys]
                            break
                    elif str(physical_port) == port:
                        media_dict = g_dict["GLOBAL_MEDIA_SETTINGS"][keys]
                        break

            elif range_separator in keys:
                if check_port_in_range(keys, physical_port):
                    media_dict = g_dict["GLOBAL_MEDIA_SETTINGS"][keys]

            # If there is a match in the global profile for a media type,
            # fetch those values
            if key[0] in media_dict:
                return media_dict[key[0]]
            elif key[1] in media_dict:
                return media_dict[key[1]]
            elif "Default" in media_dict:
                default_dict = media_dict['Default']

    media_dict = {}

    if "PORT_MEDIA_SETTINGS" in g_dict:
        for keys in g_dict["PORT_MEDIA_SETTINGS"]:
            if int(keys) == physical_port:
                media_dict = g_dict["PORT_MEDIA_SETTINGS"][keys]
                break
        if len(media_dict) == 0:
            if default_dict != 0:
                return default_dict
            else:
                logger.log_error("Error: No values for physical port '%d'"
                                  % physical_port)
            return {}
        if key[0] in media_dict:
            return media_dict[key[0]]
        elif key[1] in media_dict:
            return media_dict[key[1]]
        elif "Default" in media_dict:
            return media_dict['Default']
        elif len(default_dict) != 0:
            return default_dict
        else:
            return {}
    else:
       if default_dict != 0:
            return default_dict

def get_media_settings_key(physical_port, transceiver_dict):
    sup_compliance_str = '10/40G Ethernet Compliance Code'
    sup_len_str = 'Length Cable Assembly(m)'
    vendor_name_str = transceiver_dict[physical_port]['manufacturer']
    vendor_pn_str = transceiver_dict[physical_port]['model']
    vendor_key = string.upper(vendor_name_str) + '-' + vendor_pn_str

    media_len = ''
    if transceiver_dict[physical_port]['cable_type'] == sup_len_str:
        media_len = transceiver_dict[physical_port]['cable_length']

    media_compliance_dict_str = transceiver_dict[physical_port]['specification_compliance']
    media_compliance_dict = ast.literal_eval(media_compliance_dict_str)
    media_compliance_code = ''
    media_type = ''

    if sup_compliance_str in media_compliance_dict:
        media_compliance_code = media_compliance_dict[sup_compliance_str]

    media_type = transceiver_dict[physical_port]['type_abbrv_name']

    media_key = ''

    if len(media_type) != 0:
        media_key += media_type
    if len(media_compliance_code) != 0:
        media_key += '-' + media_compliance_code
        if len(media_len) != 0:
            media_key += '-' + media_len + 'M'

    return [vendor_key, media_key]

def get_media_val_str_from_dict(media_dict):
    media_str = ''
    lane_str = 'lane'
    lane_separator = ','
    tmp_dict={}

    for keys in media_dict:
        lane_num = int(keys.strip()[len(lane_str):])
        tmp_dict[lane_num] = media_dict[keys]

    for key in range(0, len(tmp_dict)):
        media_str += tmp_dict[key]
        if key != tmp_dict.keys()[-1]:
            media_str += lane_separator
    return media_str

def get_media_val_str(num_logical_ports, lane_dict, logical_idx):
    lane_str = "lane"
    logical_media_dict = {}
    num_lanes_on_port = len(lane_dict)

    # The physical ports has more than one logical port meaning it is
    # in breakout mode. So fetch the corresponding lanes from the file
    media_val_str = ''
    if (num_logical_ports > 1) and \
       (num_lanes_on_port >= num_logical_ports):
        num_lanes_per_logical_port = num_lanes_on_port/num_logical_ports
        start_lane = logical_idx * num_lanes_per_logical_port

        for lane_idx in range(start_lane, start_lane + \
                              num_lanes_per_logical_port):
             lane_idx_str = lane_str + str(lane_idx)
             logical_lane_idx_str = lane_str + str(lane_idx - start_lane)
             logical_media_dict[logical_lane_idx_str] = lane_dict[lane_idx_str]

        media_val_str = get_media_val_str_from_dict(logical_media_dict)
    else:
        media_val_str = get_media_val_str_from_dict(lane_dict)
    return media_val_str

def notify_media_setting(logical_port_name, transceiver_dict,
                         app_port_tbl):
    if len(media_settings) == 0:
        return

    ganged_port = False
    ganged_member_num = 1

    physical_port_list = logical_port_name_to_physical_port_list(logical_port_name)
    if physical_port_list is None:
        logger.log_error("Error: No physical ports found for "
                               "logical port '%s'" % logical_port_name)
        return PHYSICAL_PORT_NOT_EXIST

    if len(physical_port_list) > 1:
        ganged_port = True

    for physical_port in physical_port_list:
        logical_port_list = platform_sfputil.get_physical_to_logical(physical_port)
        num_logical_ports = len(logical_port_list)
        logical_idx = logical_port_list.index(logical_port_name)
        if not _wrapper_get_presence(physical_port):
            logger.log_info("Media %d presence not detected during notify"
                             % physical_port)
            continue
        if physical_port not in transceiver_dict:
            logger.log_error("Media %d eeprom not populated in "
                             "transceiver dict" % physical_port)
            continue

        port_name = get_physical_port_name(logical_port_name,
                                           ganged_member_num, ganged_port)
        ganged_member_num += 1
        key = get_media_settings_key(physical_port, transceiver_dict)
        media_dict = get_media_settings_value(physical_port, key)

        if(len(media_dict) == 0):
            logger.log_error("Error in obtaining media setting")
            return

        fvs = swsscommon.FieldValuePairs(len(media_dict))

        index = 0
        for media_key in media_dict:
            if type(media_dict[media_key]) is dict:
                media_val_str = get_media_val_str(num_logical_ports, \
                                                  media_dict[media_key],
                                                  logical_idx)
            else:
                media_val_str = media_dict[media_key]
            fvs[index] = (str(media_key), str(media_val_str))
            index += 1

        app_port_tbl.set(port_name, fvs)


def waiting_time_compensation_with_sleep(time_start, time_to_wait):
    time_now = time.time()
    time_diff = time_now - time_start
    if time_diff < time_to_wait:
        time.sleep(time_to_wait - time_diff)

# Update port SFP status table on receiving SFP change event
def update_port_transceiver_status_table(logical_port_name, status_tbl, status):
    fvs = swsscommon.FieldValuePairs([('status', status)])
    status_tbl.set(logical_port_name, fvs)

# Delete port from SFP status table
def delete_port_from_status_table(logical_port_name, status_tbl):
    status_tbl._del(logical_port_name)

# Check whether port in error status
def detect_port_in_error_status(logical_port_name, status_tbl):
    rec, fvp = status_tbl.get(logical_port_name)
    if rec:
        status_dict  = dict(fvp)
        if status_dict['status'] in errors_block_eeprom_reading:
            return True
        else:
            return False
    else:
        return False

# Init TRANSCEIVER_STATUS table
def init_port_sfp_status_tbl(stop_event=threading.Event()):
    # Connect to STATE_DB and create transceiver status table
    state_db = daemon_base.db_connect("STATE_DB")
    status_tbl = swsscommon.Table(state_db, TRANSCEIVER_STATUS_TABLE)

    # Init TRANSCEIVER_STATUS table
    logical_port_list = platform_sfputil.logical
    for logical_port_name in logical_port_list:
        if stop_event.is_set():
            break
        physical_port_list = logical_port_name_to_physical_port_list(logical_port_name)
        if physical_port_list is None:
            logger.log_error("No physical ports found for logical port '%s'" % logical_port_name)
            update_port_transceiver_status_table(logical_port_name, status_tbl, SFP_STATUS_REMOVED)

        for physical_port in physical_port_list:
            if stop_event.is_set():
                break

            if not _wrapper_get_presence(physical_port):
                update_port_transceiver_status_table(logical_port_name, status_tbl, SFP_STATUS_REMOVED)
            else:
                update_port_transceiver_status_table(logical_port_name, status_tbl, SFP_STATUS_INSERTED)


#
# Helper classes ===============================================================
#

# Thread wrapper class to update dom info periodically
class dom_info_update_task:
    def __init__(self):
        self.task_thread = None
        self.task_stopping_event = threading.Event()

    def task_worker(self):
        logger.log_info("Start DOM monitoring loop")

        # Connect to STATE_DB and create transceiver dom info table
        state_db = daemon_base.db_connect("STATE_DB")
        dom_tbl = swsscommon.Table(state_db, TRANSCEIVER_DOM_SENSOR_TABLE)
        status_tbl = swsscommon.Table(state_db, TRANSCEIVER_STATUS_TABLE)

        # Start loop to update dom info in DB periodically
        while not self.task_stopping_event.wait(DOM_INFO_UPDATE_PERIOD_SECS):
            logical_port_list = platform_sfputil.logical
            for logical_port_name in logical_port_list:
                if not detect_port_in_error_status(logical_port_name, status_tbl):
                    post_port_dom_info_to_db(logical_port_name, dom_tbl, self.task_stopping_event)
                    post_port_dom_threshold_info_to_db(logical_port_name, dom_tbl, self.task_stopping_event)

        logger.log_info("Stop DOM monitoring loop")

    def task_run(self):
        if self.task_stopping_event.is_set():
            return

        self.task_thread = threading.Thread(target=self.task_worker)
        self.task_thread.start()

    def task_stop(self):
        self.task_stopping_event.set()
        self.task_thread.join()

# Process wrapper class to update sfp state info periodically
class sfp_state_update_task:
    def __init__(self):
        self.task_process = None
        self.task_stopping_event = multiprocessing.Event()

    def _mapping_event_from_change_event(self, status, port_dict):
        """
        mapping from what get_transceiver_change_event returns to event defined in the state machine
        the logic is pretty straightforword
        """
        if status:
            if bool(port_dict):
                event = NORMAL_EVENT
            else:
                event = SYSTEM_BECOME_READY
                # here, a simple timeout event whose port_dict is empty is mapped
                # into a SYSTEM_BECOME_READY event so that it can be handled
                port_dict[EVENT_ON_ALL_SFP] = SYSTEM_BECOME_READY
        else:
            if EVENT_ON_ALL_SFP in port_dict.keys():
                event = port_dict[EVENT_ON_ALL_SFP]
            else:
                # this should not happen. just for protection
                event = SYSTEM_FAIL
                port_dict[EVENT_ON_ALL_SFP] = SYSTEM_FAIL

        logger.log_debug("mapping from {} {} to {}".format(status, port_dict, event))
        return event

    def task_worker(self, stopping_event):
        logger.log_info("Start SFP monitoring loop")

        transceiver_dict = {}
        # Connect to STATE_DB and create transceiver dom/sfp info tables
        state_db = daemon_base.db_connect("STATE_DB")
        int_tbl = swsscommon.Table(state_db, TRANSCEIVER_INFO_TABLE)
        dom_tbl = swsscommon.Table(state_db, TRANSCEIVER_DOM_SENSOR_TABLE)
        status_tbl = swsscommon.Table(state_db, TRANSCEIVER_STATUS_TABLE)

        # Connect to APPL_DB to notify Media notifications
        appl_db = daemon_base.db_connect("APPL_DB")
        app_port_tbl = swsscommon.ProducerStateTable(appl_db,
                                                     swsscommon.APP_PORT_TABLE_NAME)

        # Start main loop to listen to the SFP change event.
        # The state migrating sequence:
        # 1. When the system starts, it is in "INIT" state, calling get_transceiver_change_event
        #    with RETRY_PERIOD_FOR_SYSTEM_READY_MSECS as timeout for before reach RETRY_TIMES_FOR_SYSTEM_READY
        #    times, otherwise it will transition to "EXIT" state
        # 2. Once 'system_become_ready' returned, the system enters "SYSTEM_READY" state and starts to monitor
        #    the insertion/removal event of all the SFP modules.
        #    In this state, receiving any system level event will be treated as an error and cause transition to
        #    "INIT" state
        # 3. When system back to "INIT" state, it will continue to handle system fail event, and retry until reach
        #    RETRY_TIMES_FOR_SYSTEM_READY times, otherwise it will transition to "EXIT" state

        # states definition
        # - Initial state: INIT, before received system ready or a normal event
        # - Final state: EXIT
        # - other state: NORMAL, after has received system-ready or a normal event
        
        # events definition
        # - SYSTEM_NOT_READY
        # - SYSTEM_BECOME_READY
        #   - 
        # - NORMAL_EVENT
        #   - sfp insertion/removal
        #   - timeout returned by sfputil.get_change_event with status = true
        # - SYSTEM_FAIL
        
        # State transition:
        # 1. SYSTEM_NOT_READY
        #     - INIT
        #       - retry < RETRY_TIMES_FOR_SYSTEM_READY
        #             retry ++
        #       - else
        #             max retry reached, treat as fatal, transition to EXIT
        #     - NORMAL
        #         Treat as an error, transition to INIT
        # 2. SYSTEM_BECOME_READY
        #     - INIT
        #         transition to NORMAL
        #     - NORMAL
        #         log the event
        #         nop
        # 3. NORMAL_EVENT
        #     - INIT (for the vendors who don't implement SYSTEM_BECOME_READY)
        #         transition to NORMAL
        #         handle the event normally
        #     - NORMAL
        #         handle the event normally
        # 4. SYSTEM_FAIL
        #     - INIT
        #       - retry < RETRY_TIMES_FOR_SYSTEM_READY
        #             retry ++
        #       - else
        #             max retry reached, treat as fatal, transition to EXIT
        #     - NORMAL
        #         Treat as an error, transition to INIT


        # State           event               next state
        # INIT            SYSTEM NOT READY    INIT / EXIT
        # INIT            SYSTEM FAIL         INIT / EXIT
        # INIT            SYSTEM BECOME READY NORMAL
        # NORMAL          SYSTEM BECOME READY NORMAL
        # NORMAL          SYSTEM FAIL         INIT
        # INIT/NORMAL     NORMAL EVENT        NORMAL
        # NORMAL          SYSTEM NOT READY    INIT
        # EXIT            -

        retry = 0
        timeout = RETRY_PERIOD_FOR_SYSTEM_READY_MSECS
        state = STATE_INIT
        while not stopping_event.is_set():
            next_state = state
            time_start = time.time()
            status, port_dict = _wrapper_get_transceiver_change_event(timeout)
            logger.log_debug("Got event {} {} in state {}".format(status, port_dict, state))
            event = self._mapping_event_from_change_event(status, port_dict)
            if event == SYSTEM_NOT_READY:
                if state == STATE_INIT:
                    # system not ready, wait and retry
                    if retry >= RETRY_TIMES_FOR_SYSTEM_READY:
                        logger.log_error("System failed to get ready in {} secs or received system error. Exiting...".format((RETRY_PERIOD_FOR_SYSTEM_READY_MSECS/1000)*RETRY_TIMES_FOR_SYSTEM_READY))
                        next_state = STATE_EXIT
                    else:
                        retry = retry + 1

                        # get_transceiver_change_event may return immediately, 
                        # we want the retry expired in expected time period, 
                        # So need to calc the time diff, 
                        # if time diff less that the pre-defined waiting time, 
                        # use sleep() to complete the time.
                        time_now = time.time()
                        time_diff = time_now - time_start
                        if time_diff < RETRY_PERIOD_FOR_SYSTEM_READY_MSECS/1000:
                            time.sleep(RETRY_PERIOD_FOR_SYSTEM_READY_MSECS/1000 - time_diff)
                elif state == STATE_NORMAL:
                    logger.log_error("Got system_not_ready in normal state, treat as fatal. Exiting...")
                    next_state = STATE_EXIT
                else:
                    next_state = STATE_EXIT
            elif event == SYSTEM_BECOME_READY:
                if state == STATE_INIT:
                    next_state = STATE_NORMAL
                    logger.log_info("Got system_become_ready in init state, transition to normal state")
                elif state == STATE_NORMAL:
                    logger.log_info("Got system_become_ready in normal state, ignored")
                else:
                    next_state = STATE_EXIT
            elif event == NORMAL_EVENT:
                if state == STATE_NORMAL or state == STATE_INIT:
                    if state == STATE_INIT:
                        next_state = STATE_NORMAL
                    # this is the originally logic that handled the transceiver change event
                    # this can be reached in two cases:
                    #   1. the state has been normal before got the event
                    #   2. the state was init and transition to normal after got the event.
                    #      this is for the vendors who don't implement "system_not_ready/system_becom_ready" logic
                    for key, value in port_dict.iteritems():
                        logical_port_list = platform_sfputil.get_physical_to_logical(int(key))
                        if logical_port_list is None:
                            logger.log_warning("Got unknown FP port index {}, ignored".format(key))
                            continue
                        for logical_port in logical_port_list:
                            if value == SFP_STATUS_INSERTED:
                                logger.log_info("Got SFP inserted event")
                                # A plugin event will clear the error state.
                                update_port_transceiver_status_table(logical_port, status_tbl, SFP_STATUS_INSERTED)
                                logger.log_info("receive plug in and update port sfp status table.")
                                rc = post_port_sfp_info_to_db(logical_port, int_tbl, transceiver_dict)
                                # If we didn't get the sfp info, assuming the eeprom is not ready, give a try again.
                                if rc == SFP_EEPROM_NOT_READY:
                                    logger.log_warning("SFP EEPROM is not ready. One more try...")
                                    time.sleep(TIME_FOR_SFP_READY_SECS)
                                    post_port_sfp_info_to_db(logical_port, int_tbl, transceiver_dict)
                                post_port_dom_info_to_db(logical_port, dom_tbl)
                                post_port_dom_threshold_info_to_db(logical_port, dom_tbl)
                                notify_media_setting(logical_port, transceiver_dict, app_port_tbl)
                                transceiver_dict.clear()
                            elif value == SFP_STATUS_REMOVED:
                                logger.log_info("Got SFP removed event")
                                update_port_transceiver_status_table(logical_port, status_tbl, SFP_STATUS_REMOVED)
                                logger.log_info("receive plug out and pdate port sfp status table.")
                                del_port_sfp_dom_info_from_db(logical_port, int_tbl, dom_tbl)
                            elif value in errors_block_eeprom_reading:
                                logger.log_info("Got SFP Error event")
                                # Add port to error table to stop accessing eeprom of it
                                # If the port already in the error table, the stored error code will
                                # be updated to the new one.
                                update_port_transceiver_status_table(logical_port, status_tbl, value)
                                logger.log_info("receive error update port sfp status table.")
                                # In this case EEPROM is not accessible, so remove the DOM info
                                # since it will be outdated if long time no update.
                                # but will keep the interface info in the DB since it static.
                                del_port_sfp_dom_info_from_db(logical_port, None, dom_tbl)

                            else:
                                # SFP return unkown event, just ignore for now.
                                logger.log_warning("Got unknown event {}, ignored".format(value))
                                continue
                else:
                    next_state = STATE_EXIT
            elif event == SYSTEM_FAIL:
                if state == STATE_INIT:
                    # To overcome a case that system is only temporarily not available,
                    # when get system fail event will wait and retry for a certain period,  
                    # if system recovered in this period xcvrd will transit to INIT state
                    # and continue run, if can not recover then exit.
                    if retry >= RETRY_TIMES_FOR_SYSTEM_FAIL:
                        logger.log_error("System failed to recover in {} secs. Exiting...".format((RETRY_PERIOD_FOR_SYSTEM_FAIL_MSECS/1000)*RETRY_TIMES_FOR_SYSTEM_FAIL))
                        next_state = STATE_EXIT
                    else:
                        retry = retry + 1                   
                        waiting_time_compensation_with_sleep(time_start, RETRY_PERIOD_FOR_SYSTEM_FAIL_MSECS/1000)
                elif state == STATE_NORMAL:
                    logger.log_error("Got system_fail in normal state, treat as error, transition to INIT...")
                    next_state = STATE_INIT
                    timeout = RETRY_PERIOD_FOR_SYSTEM_FAIL_MSECS
                    retry = 0
                else:
                    next_state = STATE_EXIT
            else:
                logger.log_warning("Got unknown event {} on state {}.".format(event, state))

            if next_state != state:
                logger.log_debug("State transition from {} to {}".format(state, next_state))
                state = next_state

            if next_state == STATE_EXIT:
                os.kill(os.getppid(), signal.SIGTERM)
                break
            elif next_state == STATE_NORMAL:
                timeout = 0

        logger.log_info("Stop SFP monitoring loop")

    def task_run(self):
        if self.task_stopping_event.is_set():
            return

        self.task_process = multiprocessing.Process(target=self.task_worker,args=(self.task_stopping_event,))
        self.task_process.start()

    def task_stop(self):
        self.task_stopping_event.set()
        os.kill(self.task_process.pid, signal.SIGKILL)

#
# Daemon =======================================================================
#

class DaemonXcvrd(DaemonBase):
    def __init__(self):
        DaemonBase.__init__(self)

        self.timeout = XCVRD_MAIN_THREAD_SLEEP_SECS
        self.stop_event = threading.Event()

    # Signal handler
    def signal_handler(self, sig, frame):
        if sig == signal.SIGHUP:
            logger.log_info("Caught SIGHUP - ignoring...")
        elif sig == signal.SIGINT:
            logger.log_info("Caught SIGINT - exiting...")
            self.stop_event.set()
        elif sig == signal.SIGTERM:
            logger.log_info("Caught SIGTERM - exiting...")
            self.stop_event.set()
        else:
            logger.log_warning("Caught unhandled signal '" + sig + "'")

    # Wait for port config is done
    def wait_for_port_config_done(self):
        # Connect to APPL_DB and subscribe to PORT table notifications
        appl_db = daemon_base.db_connect("APPL_DB")

        sel = swsscommon.Select()
        sst = swsscommon.SubscriberStateTable(appl_db, swsscommon.APP_PORT_TABLE_NAME)
        sel.addSelectable(sst)

        # Make sure this daemon started after all port configured
        while not self.stop_event.is_set():
            (state, c) = sel.select(SELECT_TIMEOUT_MSECS)
            if state == swsscommon.Select.TIMEOUT:
                continue
            if state != swsscommon.Select.OBJECT:
                logger.log_warning("sel.select() did not return swsscommon.Select.OBJECT")
                continue

            (key, op, fvp) = sst.pop()
            if key in ["PortConfigDone", "PortInitDone"]:
                break

    def load_media_settings(self):
        global media_settings
        global g_dict
        (platform, hwsku_path) = self.get_path_to_platform_and_hwsku()

        media_settings_file_path = "/".join([platform, "media_settings.json"])
        if not os.path.isfile(media_settings_file_path):
            logger.log_info("xcvrd: No media file exists")
            return {}

        media_file = open(media_settings_file_path, "r")
        media_settings = media_file.read()
        g_dict = json.loads(media_settings)

    # Initialize daemon
    def init(self):
        global platform_sfputil
        global platform_chassis

        logger.log_info("Start daemon init...")

        # Load new platform api class
        try:
            import sonic_platform.platform
            import sonic_platform_base.sonic_sfp.sfputilhelper
            platform_chassis = sonic_platform.platform.Platform().get_chassis()
            logger.log_info("chassis loaded {}".format(platform_chassis))
            # we have to make use of sfputil for some features 
            # even though when new platform api is used for all vendors.
            # in this sense, we treat it as a part of new platform api.
            # we have already moved sfputil to sonic_platform_base
            # which is the root of new platform api.
            platform_sfputil = sonic_platform_base.sonic_sfp.sfputilhelper.SfpUtilHelper()
        except Exception as e:
            logger.log_warning("Failed to load chassis due to {}".format(repr(e)))

        # Load platform specific sfputil class
        if platform_chassis is None or platform_sfputil is None:
            try:
                platform_sfputil = self.load_platform_util(PLATFORM_SPECIFIC_MODULE_NAME, PLATFORM_SPECIFIC_CLASS_NAME)
            except Exception as e:
                logger.log_error("Failed to load sfputil: %s" % (str(e)), True)
                sys.exit(SFPUTIL_LOAD_ERROR)

        # Load port info
        try:
            port_config_file_path = self.get_path_to_port_config_file()
            platform_sfputil.read_porttab_mappings(port_config_file_path)
        except Exception, e:
            logger.log_error("Failed to read port info: %s" % (str(e)), True)
            sys.exit(PORT_CONFIG_LOAD_ERROR)

        # Connect to STATE_DB and create transceiver dom/sfp info tables
        state_db = daemon_base.db_connect("STATE_DB")
        self.int_tbl = swsscommon.Table(state_db, TRANSCEIVER_INFO_TABLE)
        self.dom_tbl = swsscommon.Table(state_db, TRANSCEIVER_DOM_SENSOR_TABLE)
        self.status_tbl = swsscommon.Table(state_db, TRANSCEIVER_STATUS_TABLE)

        self.load_media_settings()
        warmstart = swsscommon.WarmStart()
        warmstart.initialize("xcvrd", "pmon")
        warmstart.checkWarmStart("xcvrd", "pmon", False)
        is_warm_start = warmstart.isWarmStart()

        # Make sure this daemon started after all port configured
        logger.log_info("Wait for port config is done")
        self.wait_for_port_config_done()

        # Post all the current interface dom/sfp info to STATE_DB
        logger.log_info("Post all port DOM/SFP info to DB")
        post_port_sfp_dom_info_to_db(is_warm_start, self.stop_event)

        # Init port sfp status table
        logger.log_info("Init port sfp status table")
        init_port_sfp_status_tbl(self.stop_event)

    # Deinitialize daemon
    def deinit(self):
        logger.log_info("Start daemon deinit...")

        # Delete all the information from DB and then exit
        logical_port_list = platform_sfputil.logical
        for logical_port_name in logical_port_list:
            del_port_sfp_dom_info_from_db(logical_port_name, self.int_tbl, self.dom_tbl)
            delete_port_from_status_table(logical_port_name, self.status_tbl)

    # Run daemon
    def run(self):
        logger.log_info("Starting up...")

        # Start daemon initialization sequence
        self.init()

        # Start the dom sensor info update thread
        dom_info_update = dom_info_update_task()
        dom_info_update.task_run()

        # Start the sfp state info update process
        sfp_state_update = sfp_state_update_task()
        sfp_state_update.task_run()

        # Start main loop
        logger.log_info("Start daemon main loop")

        while not self.stop_event.wait(self.timeout):
            # Check the integrity of the sfp info table and recover the missing entries if any
            recover_missing_sfp_table_entries(platform_sfputil, self.int_tbl, self.status_tbl, self.stop_event)

        logger.log_info("Stop daemon main loop")

        # Stop the dom sensor info update thread
        dom_info_update.task_stop()

        # Stop the sfp state info update process
        sfp_state_update.task_stop()

        # Start daemon deinitialization sequence
        self.deinit()

        logger.log_info("Shutting down...")

#
# Main =========================================================================
#

def main():
    xcvrd = DaemonXcvrd()
    xcvrd.run()

if __name__ == '__main__':
    main()
